/*
 * Client.java
 *
 */
package jhelp;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.*;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static java.lang.Thread.sleep;

/**
 * Client class provides users's interface of the application.
 * @author <strong >Y.D.Zakovryashin, 2009</strong>
 * @version 1.0
 */
public class Client extends JFrame implements JHelp {

    /**
     * Static constant for serialization
     */
    public static final long serialVersionUID = 1234;
    /**
     * Private Data object presents informational data.
     */
    private Data data;

    private Socket socket;

    private ObjectInputStream input;

    private ObjectOutputStream output;

    private JHelpDataSource source;

    private ClientPanel panel;

    private Map<String, List<String>> definitions = new TreeMap(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {

            return ((String)o1).compareToIgnoreCase((String)o2);
        }
    });

    private CyclicBarrier point = new CyclicBarrier(2);

    /**
     * Constructor with parameters.
     * @param args Array of {@link String} objects. Each item of this array can
     * define any client's property.
     */
    public Client(String[] args) {

        source = new jhelp.JHelpDataSource();
        source.setClient(this);
        int i = 0;
        while (i < args.length) {
            if (args[i].length() > 3) {
                String key = args[i].substring(0,3);
                String value = args[i].substring(3,args[i].length());
                switch (key) {
                    case "-f=":
                        source.setFileName(value);
                        break;
                    case "-h=":
                        source.setHostName(value);
                        break;
                    case "-p=":
                        int port = JHelp.DEFAULT_SERVER_PORT;
                        try {
                             port = Integer.parseInt(value);
                        } catch (NumberFormatException e) {
                            System.err.println("Ошибка в номере порта, использую порт по умолчанию: )" + port);
                        }
                        source.setPort(port);
                        break;
                    default:
                        printUsage();
                        break;
                }
            }
            else {
                printUsage();
                break;
            }
            i++;
        }
        if (source.getPort() == 0) source.setPort(JHelp.DEFAULT_SERVER_PORT);
        if (source.getHostName().length() < 1) source.setHostName(JHelp.DEFAULT_SERVER);

        for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
//            System.out.println(info.getName());
            if ("Windows".equals(info.getName())) {
                try {
                    UIManager.setLookAndFeel(info.getClassName());
                } catch (ClassNotFoundException e) {
//                    System.out.println("Class Except");
                    // do nothing, set default view
                } catch (InstantiationException e) {
//                    System.out.println("Install Except");
                    // do nothing, set default view
                } catch (IllegalAccessException e) {
//                    System.out.println("Illegal Except");
                    // do nothing, set default view
                } catch (UnsupportedLookAndFeelException e) {
//                    System.out.println("Unsupport Except");
                    // do nothing, set default view
                }
                break;
            }
        }

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    panel = new ClientPanel(source);
                } catch (JHelpException e) {
                    System.out.println("Ошибка инициализации");
                    e.printStackTrace();
                    System.exit(1);
                }
                WindowAdapter adapter = new WindowAdapter() {
                    @Override
                    public void windowClosing(WindowEvent e) {
                        if (source.isConnect()) {
                            panel.setStatusLine("Программа подключена к базе данных.  Для выхода из программы закройте сначала соединение с БД");
                            return;
                        }
                        ((JFrame) e.getSource()).dispose();
                    }
                };
                JFrame frame = new JFrame("JHelp");
                frame.add(panel.getRoot());
                frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
                frame.addWindowListener(adapter);
                frame.pack();
                frame.setVisible(true);
                try {
                    point.await(1000, TimeUnit.MILLISECONDS);
                } catch (BrokenBarrierException e) {
                    //do nothing
                } catch (TimeoutException e) {
                    //do nothing
                } catch (InterruptedException e) {
                    // do nothing
                }
            }
        });
        try {
            point.await();
        } catch (BrokenBarrierException e) {
            // do nothing
        } catch (InterruptedException e) {
            // do nothing
        }

        if (source.getFileName() != null && !source.getFileName().equals("")) {
            File file = new File(source.getFileName());
            if (loadDefinitionFile(file) == JHelp.OK) {
                panel.setStatusLine("Загружено " + definitions.size() + " определений");
            }
            else panel.setStatusLine("Ошибка при загрузке файла");
        }
//        for (String key : definitions.keySet())
//            System.out.println(key);

    }

    private void printUsage() {
        System.out.println("Usage:\n\tClient -f='file_name'\n\tor Client -h='server_host_name' -p='server_port'\n" +
                            "\tor Client without args");
    }

    /**
     * Method for application start
     * @param args agrgument of command string
     */
    static public void main(String[] args) {

        Client client = new Client(args);
/*        if (client.connect(args) == JHelp.OK) {
            client.run();
            client.disconnect();
        }
*/    }

    /**
     * Method define main job cycle
     */
    public void run() {
        System.out.println("Client: run");
    }

    /**
     * Method set connection to default server with default parameters
     * @return error code
     */
    public int connect() {
        return connect(new String[] {source.getHostName(), ((Integer)Server.DEFAULT_SERVER_PORT).toString()});
    }

    /**
     * Method set connection to server with parameters defines by argument 
     * <code>args</code>
     * @return error code
     */
    public int connect(String[] args) {
        if (args.length != 2) return JHelp.ERROR;
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        try {
            socket = new Socket(host, port);
            input = new ObjectInputStream(socket.getInputStream());
            output = new ObjectOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            System.out.println("Сетевая ошибка при соединении с сервером");
//            e.printStackTrace();
            return JHelp.ERROR;
        }
        return JHelp.OK;
    }

    /**
     * Method gets data from data source
     * @param data initial object (template)
     * @return new object
     */
    public Data getData(Data data) {
        Data response = new Data(JHelp.ERROR, null, null);
        Object inObj;

        if (output != null && input != null) {
            try {
                output.writeObject(data);
                System.out.println("Send Data: "+data);
                if (data.getOperation() == JHelp.DISCONNECT) return new Data(JHelp.DISCONNECT, null, null);
                inObj = input.readObject();
                if (inObj != null && inObj instanceof Data) {
                    response = (Data)inObj;
                }
            } catch (SocketException e) {
                System.err.println("Соединение с сервером разорвано, восстанавливаю");
                if (connect(new String[] {source.getHostName(), ((Integer)source.getPort()).toString()}) == JHelp.OK) {
                    response = getData(data);
                };
            }
            catch (IOException e) {
                System.err.println("Сетевая ошибка");
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                System.err.println("Неизвестный ответ от сервера БД");
                e.printStackTrace();
            }
        }

        return response;
    }

    /**
     * Method disconnects client and server
     * @return error code
     */
    public int disconnect() {
        if (output != null && input != null) {
            getData(new Data(JHelp.DISCONNECT, null, null));
            try {
                output.close();
                input.close();
                if (socket != null && !socket.isClosed()) socket.close();
            } catch (IOException e) {
                System.out.println("Сетевая ошибка при разрыве соединения с сервером");
                e.printStackTrace();
                return JHelp.ERROR;
            }
            return JHelp.DISCONNECT;
        }
        return JHelp.ERROR;
    }

    public int loadDefinitionFile(File file) {
        Scanner input;
        try {
            input = new Scanner(file);
        } catch (FileNotFoundException e) {
            return JHelp.ERROR;
        }
        definitions.clear();
        source.setFileState(false);
        String term = "";
        String definition = "";
        boolean definitionFlag = false;
        boolean pairFlag = false;
        String tmpStr = "";
        while (input.hasNextLine()) {   // skip empty lines from begin
            tmpStr = input.nextLine();
            if (!tmpStr.trim().equals("")) break;
        }
        if (tmpStr.trim().equals("")) {
            input.close();
            return JHelp.OK; //empty file is OK
        } else {
            term += tmpStr + "\n";
        }
        while (input.hasNextLine()) {
            tmpStr = input.nextLine();
            if (tmpStr.trim().equals("")) {
                if (pairFlag) { // pair completed
                    String keyword = trimEmptyLines(term);
                    keyword = keyword.substring(0, (keyword.indexOf('\n') > JHelp.KEYWORD_MAX_LEN) ? JHelp.KEYWORD_MAX_LEN : keyword.indexOf('\n'));
                    String def = trimEmptyLines(definition);
                    List<String> entry = definitions.get(keyword);
                    if (entry == null) entry = new LinkedList<>();
                    entry.add(def);
                    definitions.put(keyword, entry);
                    pairFlag = false;
                    definitionFlag = false;
                    definition = "";
                    term = "";
                    continue;
                }
                else {
                    if (!term.equals("")) definitionFlag = true; // switch to definition constructing state
                    continue;
                }
            }
            if (definitionFlag) { //definition constructing state
                pairFlag = true;
                definition += tmpStr + "\n";
            } else {
                term += tmpStr + "\n"; //keyword constructing state
            }
        }

        input.close();
        source.setFileState(true);
        return JHelp.OK;
    }

    private String trimEmptyLines(String str) {
        String[] strs = str.split("\n");
        String out = "";
        int beg = 0;
        int end = strs.length-1;
        while (strs[beg].equals("")) beg++;
        while (strs[end].equals("")) end--;
        while (beg <= end) {
            out += strs[beg] + "\n";
            beg++;
        }
        return out;
    }

    public Data getDefinitions(String key) {
        if (source.isConnect()) { // get from database
            Data data = new Data(JHelp.SELECT, new Item(key), null);
            Data result = getData(data);
            if (result.getOperation() != JHelp.OK) {
                panel.setStatusLine("Ошибка при запросе базы данных");
                return null;
            } else {
                panel.setStatusLine("Данные от сервера получены");
                return (result.getValues() != null & result.getValues().length > 0) ? result : null;
            }
        } else {             // get from file
            List<String> def = definitions.get(key);
            if (def == null) return null;
            List<Item> resItems = new ArrayList<>(def.size());
            for (String s : def) resItems.add(new Item(s));
            return new Data(JHelp.ORIGIN, new Item(key), resItems.toArray(new Item[0]));
        }
    }

    public void copyFileToDB() {
        if (definitions == null || definitions.isEmpty()) return;
        int numError = 0;
        int numRecords = 0;
        for (String key : definitions.keySet()) {
            List<String> def = definitions.get(key);
            Item[] defItems = new Item[def.size()];
            int i = 0;
            for (String defValue : def) defItems[i++] = new Item(defValue);
            Data data = new Data(JHelp.INSERT, new Item(key), defItems);
            Data resData = getData(data);
            if (resData.getOperation() != JHelp.OK) numError++;
            numRecords++;
        }
        panel.setStatusLine("В базу данных передано " + numRecords + " записей. Успешно сохранено " + (numRecords - numError) + " записей.");
    }
}
